Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: Add stream/update event #880

Merged
merged 5 commits into from
Aug 21, 2024

Conversation

sm-sayedi
Copy link
Collaborator

@sm-sayedi sm-sayedi commented Aug 11, 2024

This was necessary for #674 to work properly (i.e., update UI based on changes in ZulipStream.channelPostPolicy).

Fixes: #182

@sm-sayedi sm-sayedi added the buddy review GSoC buddy review needed. label Aug 11, 2024
@sm-sayedi sm-sayedi requested a review from Khader-1 August 11, 2024 21:44
@gnprice
Copy link
Member

gnprice commented Aug 11, 2024

Thanks for adding this!

I'll leave this for the other rounds of review before reading in detail, but one nit in the main commit's commit message:

api: Add stream/update event

Fixes: #182
Fixes part of: #135

Let's leave the umbrella issue (#135) out of the commit message. Mentioning issues in commit messages is useful but has a trade-off: GitHub can turn it into noise on the issue thread when the commit is revised or rebased. That trade-off is worth it for the one commit or handful of commits that fix an issue, but for a broad umbrella issue like #135, the noise could get overwhelming. So just point to the specific issue #182, and let that one point to the umbrella issue.

See also: https://github.com/zulip/zulip-mobile/blob/main/docs/style.md#mention-fixed-issue

@sm-sayedi sm-sayedi force-pushed the issue-182-handle-channel-update-event branch from e0e2040 to 4ae44a6 Compare August 12, 2024 05:39
Copy link
Collaborator

@Khader-1 Khader-1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sm-sayedi! other than one small nit, LGTM.
Marking for mentor review with @hackerkid.

@@ -308,6 +308,14 @@ enum UserRole{
/// in <https://zulip.com/api/register-queue>.
@JsonSerializable(fieldRename: FieldRename.snake)
class ZulipStream {
// When adding a field to this class:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit message nit: Let's try to use more brief wording, say:

model [nfc]: Clarify how to add updatable fields to `ZulipStream`

or

model [nfc]: Clarify when to add non-final fields to `ZulipStream`

Also, I believe this is closely related to the previous commit
api: Add stream/update event
So, most probably this will get squashed into it during maintainer or integration review.

@Khader-1 Khader-1 added mentor review GSoC mentor review needed. and removed buddy review GSoC buddy review needed. labels Aug 12, 2024
@Khader-1 Khader-1 requested a review from hackerkid August 12, 2024 10:28
@sm-sayedi sm-sayedi force-pushed the issue-182-handle-channel-update-event branch from 4ae44a6 to 1765ba8 Compare August 12, 2024 16:01
@sm-sayedi sm-sayedi added the maintainer review PR ready for review by Zulip maintainers label Aug 12, 2024
@sm-sayedi sm-sayedi requested a review from chrisbobbe August 12, 2024 16:03
@sm-sayedi sm-sayedi force-pushed the issue-182-handle-channel-update-event branch 3 times, most recently from d52da57 to 6ddfcb1 Compare August 15, 2024 04:13
@chrisbobbe
Copy link
Collaborator

Is this ready for another review? Please post a comment on the PR after pushing changes that respond to a previous review, if it's ready for the next person's review.

@sm-sayedi
Copy link
Collaborator Author

sm-sayedi commented Aug 15, 2024

@chrisbobbe Yeah sure, it's ready for the maintainer review! I have requested the review through the UI and added the label, so I thought there was no need to mention it explicitly through a comment! 🙂

Also, #853 and #886 are ready for the maintainer review!

@sm-sayedi sm-sayedi force-pushed the issue-182-handle-channel-update-event branch from 6ddfcb1 to a1cc32d Compare August 16, 2024 15:22
Copy link
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Comments below.

Comment on lines 394 to 399
/// Get a [ChannelPostPolicy] from a nullable int.
///
/// Example:
/// 2 -> ChannelPostPolicy.administrators
/// null -> ChannelPostPolicy.unknown
static ChannelPostPolicy fromInt(int? value) => _byIntValue[value]!;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Get a [ChannelPostPolicy] from a nullable int.
///
/// Example:
/// 2 -> ChannelPostPolicy.administrators
/// null -> ChannelPostPolicy.unknown
static ChannelPostPolicy fromInt(int? value) => _byIntValue[value]!;
/// A [ChannelPostPolicy] from an API value or null.
static ChannelPostPolicy fromApiValueOrNull(int? value) => _byIntValue[value]!;

I think this contains all the information it needs. (And it's more specific than "int": an API value for this enum can be one of a few ints, not any int.)

(Possibly the existing fromRawString methods on other enums, like Emojiset, could have been better named fromApiValue as well.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…Ah, but see a comment elsewhere about how the null case doesn't seem necessary; so I think this could just be fromApiValue, not fromApiValueOrNull.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good change and I'd go a step further: the new name covers all the information in the doc, so that would can leave the doc out.

Comment on lines 432 to 433
case ChannelPropertyName.channelPostPolicy:
return ChannelPostPolicy.fromInt(value as int?);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case ChannelPropertyName.channelPostPolicy:
return ChannelPostPolicy.fromInt(value as int?);
case ChannelPropertyName.channelPostPolicy:
return ChannelPostPolicy.fromInt(value as int);

Do we expect to get an event with property: 'stream_post_policy' and a missing or null value? I don't think we do. In the API doc for the event and for the register response, it doesn't say we should expect missing or null, except from servers pre-3.0, and we don't support those servers.

With this change, ChannelPostPolicy.fromInt can also tightened to expect an int instead of int?.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect to get an event with property: 'stream_post_policy' and a missing or null value?

Yeah, I don't think so. But the reason I added int? instead of int is that there is ChannelPostPolicy.unknown for apiValue: null. I think it was added for backward-compatibility with server-3 at that time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. @gnprice, does that seem right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree we should expect "property": "stream_post_policy" to mean that value will be an int.

The value ChannelPostPolicy.unknown is there mainly for forward compatibility, not backward: it's "unknown" because it represents a hypothetical future server sending a value like 5 or 6 instead of 1 or 2 or 3 or 4, in which case that 5 or 6 would represent some new meaning that this client version doesn't know about.

In general zulip-flutter only supports down to Zulip Server 4:
https://github.com/zulip/zulip-flutter#editing-api-types
so we don't have any compatibility logic that's meant for server versions 3 or earlier (and stream_post_policy was introduced in Zulip Server 3). If the field were missing in some versions we still support, then we'd represent that like

  ChannelPostPolicy? channelPostPolicy;

i.e. with null to represent missing.

(Rereading the code, I think we may not have actually correctly accomplished that forward-compatibility — I think we might just throw on an unexpected value like 5. For an example I believe does work correctly, see MessageFlag.)

static ChannelPostPolicy fromInt(int? value) => _byIntValue[value]!;

// _$…EnumMap is thanks to `alwaysCreate: true` and `fieldRename: FieldRename.kebab`
static final _byIntValue = _$ChannelPostPolicyEnumMap
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alwaysCreate: true and fieldRename: FieldRename.kebab aren't passed to the JsonEnum annotation, so this comment is wrong. 🙂 Looks like it was copy-pasted from somewhere else.

_$ChannelPostPolicyEnumMap is created because ChannelPostPolicy is used as a field in a class (ZulipStream) that's annotated with JsonSerializable; see the dartdoc on JsonEnum.alwaysCreate:

  /// If `true`, `_$[enum name]EnumMap` is generated for in library containing
  /// the `enum`, even if the `enum` is not used as a field in a class annotated
  /// with [JsonSerializable].
  ///
  /// The default, `false`, means no extra helpers are generated for this `enum`
  /// unless it is used by a class annotated with [JsonSerializable].
  final bool alwaysCreate;

Probably I'd just delete the comment.

.map((key, value) => MapEntry(value, key));
}

/// The name of the [ZulipStream] properties that gets updated through [ChannelUpdateEvent.property].
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The name of the [ZulipStream] properties that gets updated through [ChannelUpdateEvent.property].
/// The name of a property of [ZulipStream] that gets updated
/// through [ChannelUpdateEvent.property].

Comment on lines 408 to 410
/// In Zulip event-handling code (for [ChannelUpdateEvent]),
/// we switch exhaustively on a value of this type
/// to ensure that every property change in [ZulipStream] responds to the event.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// In Zulip event-handling code (for [ChannelUpdateEvent]),
/// we switch exhaustively on a value of this type
/// to ensure that every property change in [ZulipStream] responds to the event.
/// In Zulip event-handling code (for [ChannelUpdateEvent]),
/// we switch exhaustively on a value of this type
/// to ensure that every property in [ZulipStream] responds to the event.


case ChannelUpdateEvent():
final ChannelUpdateEvent(:streamId, name: String streamName) = event;
assert(identical(streams[streamId], streamsByName[streamName]));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use asserts when there's an invariant we're managing purely in our own code. But here, the assert could fail because of unexpected server behavior. When we want to guard against unexpected server behavior, we don't use asserts. In production, asserts are ignored, and also, arguments to them aren't evaluated: https://dart.dev/language/error-handling#assert . We might instead throw an error, or just give up on the thing we were trying to do and move on, usually with a // TODO(log).

Here, though, we don't need the channel name on the event, do we? We can just look it up on the ZulipStream instance in our data structures. It seems potentially nice to avoid depending on the channel name in the event, to give the API more freedom to deprecate/remove that field as a cleanup.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the assert is actually for making sure our two local copies in streams and streamsByName are the same. The same assert is used in ChannelDeleteEvent.

streamName is actually useful when updating streamsByName when the event is about updating the channel name:

case ChannelPropertyName.name:
  channel.name = event.value as String;
  streamsByName.remove(streamName); 
  streamsByName[channel.name] = channel;

But if we really want to prevent getting streamName from the event, we can get it by storing channel.name in a temporary variable. Should I go with the latter option?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the assert is actually for making sure our two local copies in streams and streamsByName are the same.

I see. Then I guess here, and also where we handle ChannelDeleteEvent, it should be possible to do a targeted assert for that internal data-structure check, using nothing but the data we have locally. We can look up the channel in our streams map, get its name, look up the name in our streamsByName map, and check that the ZulipStream there matches the ZulipStream in streams.

And for the ways that unexpected server behavior can mislead our app, we can add checks that do run in production. Those ways, I think, are:

  • The channel ID in the event isn't found in our data structures (our streams map). Let's early-return in this case, as in your PR, but add a // TODO(log).
  • The event's idea of the current channel name doesn't match our data structures. We can // TODO(log) in this case, but we can probably cheerfully continue handling the event, just ignoring the name in the event to keep our streams and streamsByName consistent with each other. Since the server is giving unexpected behavior, I think there's no strong reason to trust the name in the event more than the name in our data structures. But the TODO(log) could be helpful for catching bugs where our expectations are misaligned with server behavior.

Could you send a revision with a separate commit that does this for ChannelDeleteEvent, and also does it here for ChannelUpdateEvent?

But if we really want to prevent getting streamName from the event, we can get it by storing channel.name in a temporary variable. Should I go with the latter option?

Yeah, this seems helpful. I've just checked, and the legacy app doesn't use the channel name on this event. Since it's possible to avoid using it now (just by using the name from the entry our streams map), let's avoid it, and that'll make it easy if the API wants to deprecate/remove the field in the future.

Comment on lines 206 to 220
assert(subscriptions[streamId] == null
|| identical(subscriptions[streamId], streams[streamId]));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks to me like the kind of assert we do use: checking that our own data structures are consistent with each other.

Comment on lines 242 to 243
case ChannelPropertyName.canRemoveSubscribersGroupId:
channel.canRemoveSubscribersGroup = event.value as int?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's probably OK to accept null for this one, but from reading the API docs (including the register-queue doc), I'm not sure it's ever expected, and what it would mean. The field on ZulipStream is nullable, but I think that's only for backwards compatibility with older servers that don't have the field:

  // TODO(server-6): `canRemoveSubscribersGroupId` added in FL 142
  // TODO(server-8): in FL 197 renamed to `canRemoveSubscribersGroup`
  @JsonKey(readValue: _readCanRemoveSubscribersGroup)
  int? canRemoveSubscribersGroup;

And if we're in this case, then surely we're not on one of those older servers.

So, probably nothing to do here, and no harm in being a little more permissive with int? instead of int, I think, as long as the field on ZulipStream remains nullable.

@@ -56,6 +56,17 @@ void main() {

await store.addSubscription(eg.subscription(stream1));
checkUnified(store);

await store.handleEvent(eg.channelUpdateEvent(store.streams[stream1.streamId]!,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If adding this to the 'added by events' test, maybe good to rename the test to 'added/updated by events'.

@sm-sayedi sm-sayedi force-pushed the issue-182-handle-channel-update-event branch from a1cc32d to 440f58d Compare August 19, 2024 15:33
@sm-sayedi
Copy link
Collaborator Author

Thanks @chrisbobbe for the review! New changes pushed. Please have a look.

@sm-sayedi sm-sayedi requested a review from chrisbobbe August 19, 2024 15:45
@chrisbobbe
Copy link
Collaborator

Thanks! LGTM except for a few thoughts above (one is for Greg):

#880 (comment)
#880 (comment)

and I'll move this along to integration review so Greg can take a look too.

@chrisbobbe chrisbobbe added the integration review Added by maintainers when PR may be ready for integration label Aug 19, 2024
sm-sayedi added a commit to sm-sayedi/zulip-flutter that referenced this pull request Aug 20, 2024
@sm-sayedi sm-sayedi force-pushed the issue-182-handle-channel-update-event branch from 440f58d to 00237ae Compare August 20, 2024 13:28
@sm-sayedi
Copy link
Collaborator Author

Thanks @chrisbobbe for the comments. Pushed the changes.

Now ready for the integration review!

@sm-sayedi sm-sayedi requested a review from gnprice August 20, 2024 13:30
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sm-sayedi, and thanks @chrisbobbe for the previous review!

I didn't get through a full review today, but here's the comments I have.


static ChannelPostPolicy fromApiValue(int value) => _byIntValue[value]!;

static final _byIntValue = _$ChannelPostPolicyEnumMap
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
static final _byIntValue = _$ChannelPostPolicyEnumMap
static final _byApiValue = _$ChannelPostPolicyEnumMap
  • that aligns it with the closely-related fromApiValue
  • "by int value" isn't quite accurate — one of these keys is null, not an int

Comment on lines 195 to 207
assert(identical(streams[stream.streamId], streamsByName[stream.name]));
assert(subscriptions[stream.streamId] == null
|| identical(subscriptions[stream.streamId], streams[stream.streamId]));
streams.remove(stream.streamId);
streamsByName.remove(stream.name);
subscriptions.remove(stream.streamId);
final localStream = streams[stream.streamId];
if (localStream == null) continue; // TODO(log)

final ZulipStream(:streamId, name: String streamName) = localStream;
if (streamName != stream.name) {
// TODO(log)
}
assert(identical(streams[streamId], streamsByName[streamName]));
assert(subscriptions[streamId] == null
|| identical(subscriptions[streamId], streams[streamId]));
streams.remove(streamId);
streamsByName.remove(streamName);
subscriptions.remove(streamId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like it gets rather more complicated to follow (as well as longer — 6 lines to 13 lines). There's now multiple stream IDs going around, as well as multiple stream names.

So I'm not convinced this is a helpful change. (/cc @chrisbobbe from the previous comment #880 (comment) above)

Comment on lines 219 to 220
assert(subscriptions[streamId] == null
|| identical(subscriptions[streamId], streams[streamId]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case doesn't otherwise do anything with subscriptions, so this assert can be left out.

if (streamName != event.name) {
// TODO(log)
}
assert(identical(streams[streamId], streamsByName[streamName]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also doesn't do anything with streamsByName except in the ChannelPropertyName.name case. So this assert can be moved into that case.

Then so can the streamName local.

And then I think we don't use the streamId local at all, except in doing this second streams lookup. (Which would be clearer as just saying stream, optionally with a separate assert that stream.streamId matches event.streamId — as is, if these data structures get corrupted by a bug somewhere else in our code, there's nothing local in this code to assure us that when we looked up event.streamId in streams we got a ZulipStream whose stream ID is actually event.streamId.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also doesn't do anything with streamsByName except in the ChannelPropertyName.name case. So this assert can be moved into that case.

Moved it to its specific case. But this assert in here actually helped me spot a bug in one of my previous revisions of this PR (actually when working on #886 which is stacked on top of this PR). In that revision, I forgot to include:

case ChannelPropertyName.name:
  stream.name = event.value as String;
+  streamsByName.remove(streamName);
+  streamsByName[stream.name] = stream;

So when first the channel name is changed and then some other property, i.e., channelPostPolicy, the assert would fail, simply because streamsByName was not updated accordingly. And now if we somehow have a bug where we don't update streamsByName, then the assert in the new location will not tell us about it. But now that it is covered in the tests, I think we're good to go. 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense. I do think checking for that sort of inconsistency is useful. Two places we could do that that I think would work well (and which could coexist):

  • We could use the same pattern as the checkInvariants function in test/model/message_list_test.dart: we'd write a function that scrubs the entire data structure and checks every aspect for consistency, and then have all the test cases in test/model/channel_test.dart arrange to call that function after every step they take. This is very thoroughly effective, for every situation that's exercised in those tests (even if the individual test doesn't think to check that aspect).
  • We could arrange so that in the live app (in debug mode), every access to a channel or subscription in ChannelStore asserts that _streams and _streamsByName and _subscriptions are consistent with each other about that particular channel. In that case the asserts should get factored into a single method (with "debug" at the start of its name), which we can call from various places in lib/model/channel.dart. For accesses made from the rest of the app, we can extend MapView (perhaps better yet, UnmodifiableMapView?) and add the asserts to operator[].

But I'm also content to leave those aside for now.

@JsonValue('stream_post_policy')
channelPostPolicy,
canRemoveSubscribersGroup,
canRemoveSubscribersGroupId, // TODO(Zulip-6): remove, replaced by canRemoveSubscribersGroup
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
canRemoveSubscribersGroupId, // TODO(Zulip-6): remove, replaced by canRemoveSubscribersGroup
canRemoveSubscribersGroupId, // TODO(server-8): remove, replaced by canRemoveSubscribersGroup

see other examples for the format; and in server versions 6 and 7 this does exist (in fact it's new in 6), it's version 8 where it goes away

@sm-sayedi sm-sayedi force-pushed the issue-182-handle-channel-update-event branch from 00237ae to 175239b Compare August 21, 2024 19:09
@sm-sayedi
Copy link
Collaborator Author

Thanks @gnprice for the previous review. New revisioned pushed addressing the comments. PTAL.

@sm-sayedi sm-sayedi requested a review from gnprice August 21, 2024 19:14
sm-sayedi and others added 5 commits August 21, 2024 14:30
These are the fields that gets updated via `ChannelUpdateEvent` in the
next commit.
This helps make it easy to compare the class's fields to the enum's
values and look for any discrepancies.
…sGroup

This property on streams/channels is documented as type "integer":
  https://zulip.com/api/get-streams#response

(And the same goes for its former name of canRemoveSubscribersGroupId.)

So when the server supports this property at all, and therefore might
send us an event for it, the value it supplies will be non-null.
The corresponding field on ZulipStream is nullable only because of
servers that don't yet support the property.
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update! Here's a review of the rest of the PR.

The comments were small, so in the interest of efficiency I went ahead and made the changes; merging, with a few added commits on top:
1ee4bb6 api [nfc]: Put ChannelPropertyName right after ZulipStream
03f385a api [nfc]: Explain each missing ChannelPropertyName inline
fc3db42 api: Tighten value type on ChannelUpdateEvent for canRemoveSubscribersGroup

I also replied to a subthread above at #880 (comment) (didn't spot it earlier).

@JsonValue('stream_post_policy')
channelPostPolicy,
canRemoveSubscribersGroup,
canRemoveSubscribersGroupId, // TODO(sever-8): remove, replaced by canRemoveSubscribersGroup
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
canRemoveSubscribersGroupId, // TODO(sever-8): remove, replaced by canRemoveSubscribersGroup
canRemoveSubscribersGroupId, // TODO(server-8): remove, replaced by canRemoveSubscribersGroup

This is one place a comment's spelling is actually important, because it's here for use in grep 🙂

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, missed it! Thanks for catching it.

@@ -382,6 +390,51 @@ enum ChannelPostPolicy {
final int? apiValue;

int? toJson() => apiValue;

static ChannelPostPolicy fromApiValue(int value) => _byApiValue[value]!;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's have the same method be used in both of the places we deserialize a ChannelPostPolicy — so not only in the event but in ZulipStream too

For examples, git grep -B2 -A20 ^enum lib/api/, and pick one of the other enums that has a fromRawString or similar method and look at where that enum is used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

… Hmm, there's actually a shortage of enums that work quite like this. (Where it's a numeric value, rather than a string that's the enum value's name, and where at the same time we have a named method we call to deserialize it rather than always doing so as a field on a JsonSerializable class.)

Our enum handling in general feels a bit messy — and I think that's largely because json_serializable is a bit messy in its API for enums — but this particular one is fine for now. Later sometime we can try to find a nicer pattern and sweep through cleaning them up.

Comment on lines 434 to 438
case ChannelPropertyName.canRemoveSubscribersGroup:
case ChannelPropertyName.canRemoveSubscribersGroupId:
case ChannelPropertyName.streamWeeklyTraffic:
return value as int?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to #880 (comment) : canRemoveSubscribersGroup and its old name should have int values, not int?, because those properties are never null on servers new enough to have them.

(OTOH streamWeeklyTraffic can be null on a stream/channel: https://zulip.com/api/get-streams#response so its value in this event can potentially be null too.)

@gnprice gnprice force-pushed the issue-182-handle-channel-update-event branch from 175239b to fc3db42 Compare August 21, 2024 21:34
@gnprice gnprice merged commit fc3db42 into zulip:main Aug 21, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration review Added by maintainers when PR may be ready for integration maintainer review PR ready for review by Zulip maintainers mentor review GSoC mentor review needed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Handle updated streams (stream op: update events)
4 participants